package com.lightsh.gui;

import java.util.ArrayList;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.ContactListener;
import org.jbox2d.dynamics.DestructionListener;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.ContactPoint;
import org.jbox2d.dynamics.contacts.ContactResult;
import org.jbox2d.dynamics.joints.Joint;

import com.lightsh.body.BaseBody;
import com.lightsh.body.CircleImpl;
import com.lightsh.body.PolygonImpl;
import com.lightsh.body.RectangleImpl;
import com.lightsh.configure.Configure;
import com.lightsh.interfaces.creator.IBodyCreator;
import com.lightsh.test.PerformanceTester;
import com.lightsh.utility.Constants;
import com.lightsh.utility.Debugger;
import com.lightsh.utility.Utility;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.shapes.RectShape;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable.Creator;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

/**
 * 该类实现了jbox2d的图形世界对象的管理，主要包括物理对象的创建/更新/绘制/删除等操作
 * <br/>该类不响应用户动作事件
 * <br/>注意: 该类需要调用{@link #createWorld()}来初始化物理世界
 * @author Light
 *
 */
public class PhysicsScreen extends TransferScreen{
	
	private static final String TAG = "PhysicsScreen";
	private static final boolean DEBUG = true;
	
	private World mWorld;
	private AABB mAABB;
	
	protected boolean mGravitySensorEnable = false;
	private final Vec2 mGravityVec2;
	
	// 设置的世界高宽,以px为单位
	protected int mWorldW;
	protected int mWorldH;	
	
	/**
	 * 是否会在静止后(没有外力作用的情况下)不再模拟
	 * <br/>例如，当该值为true，物体在重力作用下静止。你改变重力方向{@link World#setGravity(Vec2)}后，{@link World#step(float, int)}不会使物体动起来，因为没有外力作用，物体不会从sleep状态中恢复。
	 */
	private boolean mDoSleep = true;
	private int mIterations = 10;
	/**
	 * 屏幕与真实世界的比例, meter/px 的对比
	 */
	private float mMeterDividePx = 1/10f;
	
//	ArrayList<BaseBody> mBodyList = null;
	
	/**
	 * 初始化screen，初始化屏幕的高宽和视图的总高宽(能显示的总范围)，并做了一些常量的初始换，并没用真正的构建了{@link World}。
	 * <br/>需要在构建world时，调用{@link #createWorld(int, int, float, boolean)}来创建实际的world
	 * @param context			程序的上下文
	 * @param screenW			屏幕的宽度，以px为单位
	 * @param screenH			屏幕的高度，以px为单位
	 * @param totalScreenW		屏幕的总宽度，需要大于或者等于screenW
	 * @param totalScreenH		屏幕的总高度，需要大于或者等于screenH
	 */
	public PhysicsScreen(Context context, int screenW, int screenH, int totalScreenW, int totalScreenH) {
		super(context, screenW, screenH, totalScreenW, totalScreenH);
		// 初始化相关的数据
//		mBodyList = new ArrayList<BaseBody>();
		mGravityVec2 = new Vec2();		
	}
	@Override
	public void onDraw(Canvas canvas) {
		// 由父类负责绘制背景
		super.onDraw(canvas);
		// 画各个body
		final Rect rect = getCurrentRectClone();
		final float worldScale = getScreenScale();		
		
//		for(BaseBody body : mBodyList){
//			body.draw(canvas, rect, mMeterDividePx, worldScale);
//		}
		
		// FIXME 优化这里的绘制方法
		Utility.fixme();
		int currentLevel = -1;
		int nextLevel = 0;
		int level = 0;
		Body body = null;
		BaseBody baseBody = null;
		
		while(currentLevel != nextLevel){
			currentLevel = nextLevel;
			body = mWorld.getBodyList();
			while(body != null && body.m_userData != null){
				baseBody = (BaseBody)body.getUserData();
				level = baseBody.getDrawLevel();
				if(level == currentLevel){
					baseBody.draw(canvas, rect, getMeterDividePx(), worldScale);	
				}else if(level > currentLevel){
					// 记录与currentLevel最接近的下一个level
					if(currentLevel == nextLevel || nextLevel > level){
						nextLevel = level;
					}
				}
				body = body.getNext();
			}
			
		}
		
		
//		Body body = mWorld.getBodyList();
//		while(body != null && body.m_userData != null){
//			BaseBody baseBody = (BaseBody)body.getUserData();
//			baseBody.draw(canvas, rect, getMeterDividePx(), worldScale);		
//			body = body.getNext();
//		}
	}
	@Override
	public void onUpdate() {
		super.onUpdate();
		if(!isPause()){
			// 模拟整个世界的下一个状态
			if(isSensorEnable() && mGravitySensorEnable){
				mGravityVec2.x = mXGravity * 10;
				mGravityVec2.y = mYGravity * 10;
				
				mWorld.setGravity(mGravityVec2);
			}
			// 动态加入body可能会失败,我们需要保证的是，所有的world操作都在同一个线程中进行！！
			mWorld.step(getTimeStep(), mIterations);
			
			// 更新各个body的状态
			Body body = mWorld.getBodyList();
			while(body != null && body.m_userData != null){
				((BaseBody)body.getUserData()).update();	
				body = body.getNext();
			}
//			for(BaseBody body : mBodyList){
//				body.update();
//			}
		}
	}
	
	/**
	 * 初始化世界,需要提供需要创建的世界的大小以及该世界与实际屏幕px的比例。
	 * <br/>注意，这里的屏幕大小是实际设备的屏幕尺寸，使用px为单位，而世界大小是需要
	 * 整个游戏界面能够扩展到的总大小，也是以px为单位。在该世界大小之外的物体将会被自动清除，也无法显示。
	 * <br/>注意，使用box2d模拟时，模拟的物体(指在模拟世界中移动的物体)在0.1米到10米之间性能最好。所以，在设计世界与屏幕的
	 * meterDivdePx比例参数时，需要考虑，尽量将需要模拟的物体控制在：
	 * <h3> 0.1<=“物体需要显示的实际像素大小"*"meterDivdePx"<=10 </h3>
	 * <br/>因为之后的body创建的接口都是使用px为单位的，所以这个meterDevidePx的设置至关重要，请认真考虑，否则会影响性能。
	 * @param worldW			需要创建的世界的宽度，px为单位
	 * @param worldH			需要创建的世界的高度，px为单位
	 * @param meterDevidePx		实际世界的单位m与屏幕px的高度,即每px代表多少米(例如该值为1/10，则表示屏幕上一个px，代表实际的0.1米)
	 * @param doSleep 			是否会在静止后(没有外力作用的情况下)不再模拟。例如，当该值为true，物体在重力作用下静止。你改变重力方向{@link World#setGravity(Vec2)}后，{@link World#step(float, int)}不会使物体动起来，因为没有外力作用，物体不会从sleep状态中恢复。
	 */
	public void createWorld(int worldW, int worldH, float meterDevidePx, boolean doSleep){
		if(mWorld != null){
			throw new RuntimeException("world had already created!");
		}
		mWorldW = worldW;
		mWorldH = worldH;
		mMeterDividePx = meterDevidePx;
		mDoSleep = doSleep;
		
		mAABB = new AABB();
		mAABB.lowerBound.set(0, 0);
		mAABB.upperBound.set(mWorldW * getMeterDividePx(), mWorldH * getMeterDividePx());		
		mWorld = new World(mAABB, new Vec2(0, 0), mDoSleep);
		
		mWorld.setDestructionListener(new DestructionListener() {
			
			@Override
			public void sayGoodbye(Shape shape) {
//				logTip("sayGoodbye shape　" + shape);
				
			}
			
			@Override
			public void sayGoodbye(Joint joint) {
				logTip("sayGoodbye joint " + joint);
				
			}
		});
		mWorld.setContactListener(new ContactListener() {
			
			@Override
			public void result(ContactResult point) {
//				logTip("result " + point);
				((BaseBody)(point.shape1.m_body.m_userData)).onContactResult(point);
				((BaseBody)(point.shape2.m_body.m_userData)).onContactResult(point);
				
			}
			
			@Override
			public void remove(ContactPoint point) {
//				logTip("remove " + point);
				((BaseBody)(point.shape1.m_body.m_userData)).onContactRemove(point);
				((BaseBody)(point.shape2.m_body.m_userData)).onContactRemove(point);
				
			}
			
			@Override
			public void persist(ContactPoint point) {
//				logTip("persist " + point);
				((BaseBody)(point.shape1.m_body.m_userData)).onContactPersist(point);
				((BaseBody)(point.shape2.m_body.m_userData)).onContactPersist(point);
			}
			
			@Override
			public void add(ContactPoint point) {
//				logTip("add " + point);
				((BaseBody)(point.shape1.m_body.m_userData)).onContactAdd(point);
				((BaseBody)(point.shape2.m_body.m_userData)).onContactAdd(point);
			}
		});
	}
	
	/**
	 * 跟{@link #createWorld(int, int, float, boolean)}做同样的工作，区别在于不指定world的高宽，使用屏幕可显示的总高宽(totalScreenW, totalScreenH)作为world的高宽
	 */
	public void createWorld(float meterDevidePx, boolean doSleep){
		createWorld(getTotalScreenWidth(), getTotalScreenHeight(), meterDevidePx, doSleep);
	}
	
	public boolean isGravitySensorEnabl(){
		return mGravitySensorEnable;
	}
	public void enableGravitySensor(boolean enable){
		mGravitySensorEnable = enable;
	}
	
	public int getWorldHeight(){
		return mWorldH;
	}
	public int getWorldWidth(){
		return mWorldW;
	}
	
	private void logTip(String tip){
		Log.d(TAG, tip == null ? "" : tip);
	}
	
	public World getWorld(){
		return mWorld;
	}
	
	public float getMeterDividePx(){
		return mMeterDividePx;
	}
	
	/**
	 * 根据screen设置的比例值，将px转换成对应的meter值
	 * @param px		需要转换的px值
	 * @return			目标meter值
	 */
	public float pxToMeter(float px){
		return px * mMeterDividePx;
	}
	/**
	 * 根据screen设置的比例值，将meter转换成对应的px值
	 * @param px		需要转换的meter值
	 * @return			目标px值
	 */
	public float meterToPx(float meter){
		return meter / mMeterDividePx;
	}
	
	/**
	 * 目标矩形是否在world范围内
	 * @param rect		目标矩形,该单位为meter
	 * @return			如果在world范围内，返回true
	 */
	public boolean isInWorld(RectF rect){
		
		rect.left = meterToPx(rect.left);
		rect.top = meterToPx(rect.top);
		rect.bottom = meterToPx(rect.bottom);
		rect.right = meterToPx(rect.right);
		
		if(rect.right <= 0 || rect.left >= mWorldW || rect.top >= mWorldH || rect.bottom <= 0){
//			logTip(rect + " out!");
			return false;
		}
		return true;
	}
	@Override
	protected void onDrawTester(Canvas canvas) {
		if(mEnablePerTest){
			mPerTester.drawPerformanceInfo(canvas, mWorld);
		}
	}
	@Override
	public void clearAllList() {
		super.clearAllList();
		cleanAllBody();
	}
	
	private void cleanAllBody(){
		Body body = getWorld().getBodyList();
		while(body != null && body.m_userData != null){
			getWorld().destroyBody(body);
			body = body.getNext();
		}
	}
	
}
